package core.online;

import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

import net.ByteReader;
import net.ClientSideConnection;
import net.Connection;
import net.Packet;
import net.Protocol;
import net.ServerInfo;
import net.TCPConnectionListener;
import net.UDPConnectionListener;
import util.Logger;
import core.Entity;
import core.VoidGameClient;

/**
 * Use for online games.
 * 
 * @author Jason
 * 
 */
public abstract class VoidOnlineGameClient extends VoidGameClient implements
		UDPConnectionListener, TCPConnectionListener, Protocol {

	protected ClientSideConnection matchMakingConnection;
	private ClientSideConnection gameHostConnection;
	protected Queue<Packet> packetsReceived;
	private boolean verboseNetworkOutput = false;
	private boolean gameRunning = false;

	public VoidOnlineGameClient() {
		// connect to the server
		matchMakingConnection = new ClientSideConnection(getServerIP(),
				Protocol.MATCH_MAKING_PORT);
		try {
			matchMakingConnection.connect();
			matchMakingConnection.addTCPConnectionListener(this);
			matchMakingConnection.addUDPConnectionListener(this);
			// startGame();
		} catch (Exception e) {
			Logger.log(e);
		}
	}

	@Override
	protected void startGame() {
		super.startGame();
		packetsReceived = new LinkedList<Packet>();
		altBuffer = new LinkedList<Packet>();
	}

	protected ClientSideConnection getServerConnection() {
		if (gameHostConnection == null) {
			gameHostConnection = new ClientSideConnection(new ServerInfo(null,
					0));
		}
		return gameHostConnection;
	}

	@Override
	public void shutdown() {
		if (matchMakingConnection != null) {
			matchMakingConnection.exit();
			matchMakingConnection = null;
		}
		if (gameHostConnection != null) {
			gameHostConnection.exit();
			gameHostConnection = null;
		}
	}

	Queue<Packet> altBuffer;

	@Override
	protected void updateEngine() {
		Queue<Packet> temp = packetsReceived;
		packetsReceived = altBuffer;
		altBuffer = temp;
		while (altBuffer.isEmpty() == false) {
			Packet packet = altBuffer.poll();
			ByteReader reader = new ByteReader(packet.data);
			while (!reader.isDone()) {
				int id = reader.peekInt();
				Entity e = getEntity(id);
				if (e == null) {
					Logger.log(Logger.FINER, "Entity added to engine.");
					e = Entity.create(reader);
					addNewEntity(e);
				} else {
					if (e instanceof OnlineEntity) {
						Entity serverCopy = Entity.create(reader);
						((OnlineEntity) e).driftTo(serverCopy);
					} else {
						// update the attributes of this entity
						// discard the ID and class-type because we don't need
						// them
						// now
						reader.readInt();
						reader.readString();
						e.readAttributes(reader);
						if (verboseNetworkOutput) {
							Logger.log(Logger.FINER, "Entity updated:");
							Logger.log(Logger.FINEST, e.toString());
						}
					}
				}
			}
		}
	}

	public abstract String getServerIP();

	@Override
	public void tcpConnectionBroken(Connection conn) {
	}

	@Override
	public void tcpConnectionMade(Connection conn) {
	}

	@Override
	public void tcpMessageReceived(byte[] bytes, Connection from) {
		if (bytes[0] == Protocol.PING_TCP) {
			from.sendTCP(bytes);
			return;
		} else if (!gameRunning) {
			String msg = new String(bytes);
			String[] m = msg.split(DELIM);
			String command = m[0];
			if (command.equals(JOIN_SERVER)) {

				String ip = m[1];
				int port = Integer.valueOf(m[2]);
				Logger.log(Logger.NORMAL, "Matchmaking server is redirecting"
						+ " us to a Game Server at " + ip + ":" + port);
				gameHostConnection.setInfo(ip, port);
				try {
					gameHostConnection.connect();
					gameHostConnection.addTCPConnectionListener(this);
					gameHostConnection.addUDPConnectionListener(this);
					Logger.log(Logger.NORMAL,
							"Successfully connected to Game Host.");
				} catch (IOException e) {
					Logger.log(e, "Could not join game host server.");
				}
			} else if (command.equals(START_GAME)) {
				Logger.log(Logger.NORMAL, "Server says to Start Game!");
				gameRunning = true;
				beginOnlineGame();
			} else {
				handleTCPMessage(bytes, from);
			}
		} else {
			handleTCPMessage(bytes, from);
		}
	}

	@Override
	public void udpConnectionBroken(Connection conn) {
	}

	@Override
	public void udpConnectionMade(Connection conn) {
	}

	@Override
	public void udpMessageReceived(byte[] bytes, Connection from) {
		if (bytes[0] == Protocol.PING_UDP) {
			if (verboseNetworkOutput) {
				Logger.log(Logger.FINE, "Received UDP Ping Message");
			}
			from.sendUDP(bytes);
			return;
		}
		if (verboseNetworkOutput) {
			Logger.log(Logger.FINE, "Received Entity Update Packet.");
		}
		packetsReceived.add(new Packet(from, bytes));
	}

	public void setVerboseNetworkOutput(boolean b) {
		this.verboseNetworkOutput = b;
	}

	/**
	 * Called when the server has announced that it is time to start the game.
	 */
	protected abstract void beginOnlineGame();

	protected abstract void handleTCPMessage(byte[] bytes, Connection from);

}
